#include <slothttpd/config/formatter.hpp>
#include <slothttpd/config/server.hpp>

#include <stdexcept>
#include <iostream>

#include <boost/asio/buffer.hpp>
#include <boost/array.hpp>
#include <boost/algorithm/string.hpp>

namespace slothttpd {
namespace config {

const char formatter::escape = '%';

/*
 * %m - request method
 * %p - server path (like "/srv/http/example.org")
 * %u - request path aka "script_name" (like "/index.php")
 * %q - query string (like "key=value&key2=value2")
 * %l - content length
 * %t - content type
 */
formatter::formatter(const std::string &format, base_location& loc)
{
    std::string constant;
    
    enum state_type {
        state_normal,
        state_escape
    };
    
    state_type state_ = state_normal;
    
    for (char c : format) {
        switch (state_) {
        case state_normal:
            if (c != escape) {
                constant.push_back(c);
            } else {
                state_ = state_escape;
            }
            break;
            
        case state_escape:
            if (c != escape) {
                part_string_type p;
                
                switch (c) {
                case 'm':
                    p = [=](char_range_t &dest, http::request &req) {
                        return place_range(dest, req.method_name);
                    };
                    break;
                    
                case 'p': {
                        auto host_path = loc.path;
                        
                        p = [=](char_range_t &dest, http::request &req) {
                            return place_range(dest, host_path);
                        };
                    }
                    break;
                    
                case 'u':
                    p = [=](char_range_t &dest, http::request &req) {
                        return place_range(dest, req.path);
                    };
                    break;
                    
                case 'q':
                    p = [=](char_range_t &dest, http::request &req) {
                        return place_range(dest, req.query_string); 
                    };
                    break;
                
                case 'l':
                    p = [=](char_range_t &dest, http::request &req) {
                            if (req.content == nullptr) {
                                static std::string zero = "0";
                                
                                return place_range(dest, zero);
                            } else {
                                // TODO: remove magic number
                                boost::array<char, 20> buf;
                                auto n = std::snprintf(buf.data(), buf.size(), "%lu", (unsigned long) req.content->length);
                                return place_range(dest, char_range_t(buf.data(), buf.data() + n));
                            }
                    };
                    break;
                
                case 't':
                    p = [=](char_range_t &dest, http::request &req) {
                        return place_range(dest, req.content_type);
                    };
                    break;
                    
                default:
                    std::cerr << "unknown escape character: " << c << std::endl;
                    
                    constant.push_back(escape);
                    constant.push_back(c);
                    break;
                }
                
                if (!p.empty()) {
                    push_constant_action(constant);
                    constant.clear();
                    actions_.push_back(p);
                }
            } else {
                constant.push_back(escape);
                constant.push_back(escape);
            }
            state_ = state_normal;
            break;
        }
    }
    
    if (!constant.empty()) {
        push_constant_action(constant);
    }
}

std::size_t formatter::place(char_range_t &dest, http::request &req) const
{
    for (auto &act : actions_) {
        std::size_t bytes_left = act(dest, req);
        if (bytes_left > 0) {
            return bytes_left;
        }
    }
    
    return 0;
}

std::size_t formatter::size(http::request &req) const
{
    char_range_t empty;
    std::size_t retval = 0;
    
    for (auto &act : actions_) {
        retval += act(empty, req);
    }
    
    return retval;
}

void formatter::push_constant_action(const std::string &str)
{
    actions_.push_back([=](char_range_t &dest, http::request &req) {
            return place_range(dest, str);
    });
}

}
}
